package com.nononsenseapps.feeder.net

import android.app.*
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat
import com.gitee.wsl.android.ext.IntentAction
import com.gitee.wsl.ext.*
import com.gitee.wsl.android.ext.broadcastPendingIntent
import com.gitee.wsl.android.ext.buildNotificationService
import com.gitee.wsl.android.ext.notificationServiceCanceled
import com.gitee.wsl.jvm.net.server.HttpCallHandler
import com.gitee.wsl.jvm.net.server.HttpCallHandlerFactory
import com.koushikdutta.async.callback.CompletedCallback
import com.koushikdutta.async.http.AsyncHttpPut
import com.koushikdutta.async.http.body.JSONObjectBody
import com.koushikdutta.async.http.body.StringBody
import com.koushikdutta.async.http.server.*
import com.nononsenseapps.feeder.core.BuildConfig
import com.nononsenseapps.feeder.db.entity.Feed
import com.nononsenseapps.feeder.db.entity.FeedItem
import com.nononsenseapps.feeder.db.room.*
import com.nononsenseapps.feeder.model.ACTION_STOP_HTTP_SERVER
import com.nononsenseapps.feeder.model.RssNotificationBroadcastReceiver
import com.gitee.wsl.jvm.net.server.HttpUriRooterManager
import com.gitee.wsl.jvm.net.server.IHTTPSession
import com.gitee.wsl.jvm.net.server.Response
import com.gitee.wsl.struct.router.Method
import com.gitee.wsl.struct.router.createUriPattern
import com.nononsenseapps.feeder.parser.opml.writeOutputString
import com.nononsenseapps.feeder.util.getIp
import com.nononsenseapps.feeder.util.toJsonFeed
import com.nononsenseapps.feeder.util.toSyndFeed
import com.nononsenseapps.jsonfeed.feedAdapter
import com.rometools.rome.io.SyndFeedOutput
import org.joda.time.DateTime
import org.joda.time.DateTimeZone
import org.json.JSONObject
import timber.log.Timber
import java.io.InputStream
import java.net.CookieHandler
import java.net.URL
import java.util.*

const val  PortNumber=8080
const val  OPML_EXPORT_PATH="/feed.opml"
const val  RSS_EXPORT_PATH="/feed"

class HttpService : Service() {

    val server by lazy {
        Http(this).apply {
            this.errorCallback = CompletedCallback { Timber.d("rss server completed") }
        }
    }

    var running = false;

    val feedDao by lazy { AppDatabase.getInstance(this).feedDao() }
    val feedItemDao by lazy { AppDatabase.getInstance(this).feedItemDao() }

    override fun onBind(intent: Intent?): IBinder? {
        return null
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Timber.d("http server start command:$flags")
       /* startServer()
        return START_STICKY*/
        when (intent?.action) {
            IntentAction.stop -> onDestroy()
            else -> startServer()
        }
        return super.onStartCommand(intent, flags, startId)
    }

    override fun onCreate() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val notification = rssServiceNotification(this)
            startForeground(RSS_ServiceNotification_ID,notification)
        }

        server["/", HttpServerRequestCallback { _, response ->
            Timber.d("in Come /")
            response.send("""
                <!doctype html>
  <html>
  <head>
  <title>Feeder Http Server</title>
  </head>
  <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
  <body>
  <form id="favoriteForm">                    
   <table class="table-edit" width="80%" align="center">
   <tr>
   <td>
       <p>Feed Rss http Service is running!!!</p>
       <p>   本地订阅: (通用格式.<a href="/feed">/feed</a>?id={订阅ID}&xml={格式xml:true,json:false}&page={页码|all}   <a href="/feed.opml">/feed.opml</a></p>
       <p>   -- 收藏:<a href="/rss.xml">/rss.xml</a> 稍后阅读:<a href="/feed">/feed</a>	备忘录:<a href="/memo.xml">/memo.xml</a>	 </p>
       <p>   -- 查询订阅: /search?text={查询内容}&xml={格式xml:true,json:false}&page={页码} </p>
       <p>   收藏 新增地址（post方式）： /favorite/new  类型application/json</p>
   </td>
   </tr>
   <tr>
   <td>
             <p> 收藏网址   ：  <input name="url" type="url" size="100"/> </p>
             <p> 收藏标题   ：  <input name="title"  type="text"  size="100"/> </p>
             <p>备注(memo) ：</p>   
             <p> <textarea name="description" rows="20" cols="100"></textarea> </p>
   </td>
   </tr>
     <tr>
      <td><input type="reset" value="重置">       <a id="saveToServer" href="#">加入收藏</a>
          <script type="text/javascript">
              ${'$'}(function(){                                                
                    ${'$'}.fn.serializeJson=function(){  
                    　　// 定义一个json对象
                    　　var serializeObj={};
                   　　// this 就是谁调用，就把谁转换为数组
                   　　var array=this.serializeArray();
                   　　//遍历数组，
                   　　${'$'}(array).each(function(){
                   　　　　 //判断是否有对应的名称                        
                    　　　　if(serializeObj[this.name]){  
                   　　　　 　　　　//判断这个名称是否有值，若有多个值，则多个值存到一个数组中                  
                                 if(${'$'}.isArray(serializeObj[this.name])){ 
                         　　　　　　　　//若有值，则追加。              
                                      serializeObj[this.name].push(this.value);  
                         　　　　　}else{  
                                                         
                                      serializeObj[this.name]=[serializeObj[this.name],this.value];  
                            　　　}  
                  　　　　　}else{
                      　　　　　　// 若无对应的名称 ,则创建名称。然后赋值                
                                serializeObj[this.name]=this.value;   
                      　　 }  
                 　　});  
                  　 return serializeObj;  
              　　};
 
           //给查询按钮（searchBtn） 添加点击事件                                     
           ${'$'}('#saveToServer').click(function(){
             //1、将表单转换成json对象
            var formData=${'$'}('#favoriteForm').serializeJson();
            ${'$'}.ajax({  
                   type: "POST",  
                    url: "/favorite/new",  
                    contentType: "application/json; charset=utf-8",  
                   data: JSON.stringify(formData),  
                   dataType: "text",  
                 success: function (message) {  
                     alert("收藏成功!"+message);
                     
                  },  
                 error: function (message) {  
                    alert("提交数据失败！"+message);  
                 }  
                });  
            });  
          });
         </script>
       </td>
    </tr>
   </table>
 </form>
 </body>
 </html>
            """.trimIndent())
        }]

        server.post("/favorite/new", HttpServerRequestCallback { request, response ->
            Timber.d("in Come favorite new/")
            try {
                request.getBody<JSONObjectBody>()?.get()?.let { jsonObject ->
                    Timber.d("in Come favorite new json:$jsonObject")
                    val feedItem = FeedItem()
                    feedItem.apply {
                        jsonObject.optString("url").let { this.link = it }
                        jsonObject.optString("title").let {
                            this.title = it
                            this.plainTitle = it
                        }
                        jsonObject.optString("description").let {
                            this.description = it
                            this.plainSnippet = it.take(200)
                        }
                        jsonObject.optString("tag").let {
                            this.itemTags = it
                        }
                        this.guid = UUID.randomUUID().toString()
                        this.pubDate = DateTime.now(DateTimeZone.UTC)
                        this.feedId = FAVORITE_FEED_ID
                    }
                    val id = feedItemDao.upsert(feedItem)
                    response.send("Feed Feed install success id:${id}")
                    return@HttpServerRequestCallback
                }
            } catch (e: Exception) {
                response.code(500)
                response.send("Feed install Error:${e.printStackTrace()}")
            }
            response.send("format error,please use item json ")
        })

        server["/rss.json", HttpServerRequestCallback { request, response ->
            responseFeedToRss( request,response, feedId = FAVORITE_FEED_ID,isXML = false )
        }]

        server["/rss.xml", HttpServerRequestCallback { request, response ->
            responseFeedToRss( request,response, feedId = FAVORITE_FEED_ID,isXML = true )
        }]


        server["/memo.xml", HttpServerRequestCallback { request, response ->
            responseFeedToRss( request,response, feedId = MEMO_FEED_ID,isXML = true )
        }]

        server["/feed", HttpServerRequestCallback { request, response ->
            val feedId=request["id"]?.toLongOrNull()?: READ_LATE_FEED_ID
            val toXml=request["xml"]?.toBooleanStrictOrNull()?:true
            responseFeedToRss( request,response, feedId = feedId,isXML = toXml )
        }]

        server["/search", HttpServerRequestCallback { request, response ->
            val toXml=request["xml"]?.toBooleanStrictOrNull()?:true
            responseSearchToRss( request,response,toXml)
        }]

        server[OPML_EXPORT_PATH, HttpServerRequestCallback { request, response ->
            Timber.d("in Come feed opml")
            val isLocal = request["local"]?.toBooleanStrictOrNull()?:false
            val retStringBuilder = StringBuilder()
            writeOutputString(retStringBuilder, feedDao.loadTags()) { tag ->
                val feedList=feedDao.loadFeeds(tag = tag)
                if(isLocal)
                    feedList.forEach { it.url= URL(getRunAddr(this)+"/feed?id="+it.id) }
                feedList
            }
            response.setContentType("application/binary")
            response.send(retStringBuilder.toString())
        }]
        
        server.addAction(AsyncHttpPut.METHOD,"/fever", HttpServerRequestCallback { request, response ->  })
            
        server.post("/fever", HttpServerRequestCallback { request, response ->
            request["action"]?.let{
                   if("login" == it){
                       val username=request["username"]
                       val password=request["password"]
                       //"fever_auth"
                      response.send(JSONObject(mapOf("api_version" to 3,"auth" to 1)))
                   }
            }
            request["mark"]?.let{ type ->
                val unread_recently_read= request["unread_recently_read"]
                when(type){
                     "item"->{
                         val itemId=request["id"]
                         request["as"]?.let{
                             when(it){
                                 "read"->{

                                 }
                                 "saved"->{

                                 }
                                 "unsaved"->{

                                 }
                             }
                         }
                     }
                     "group"->{
                         val action=request["as"]
                         val id=request["id"]
                         val before=request["before"]
                     }
                     "feed"->{
                         val action=request["as"]
                         val id=request["id"]
                         val before=request["before"]
                     }
                 }
            }

        })

        server.get("/fever", HttpServerRequestCallback { request, _ ->
            var isXml = false
            request["api"]?.let {
                isXml = "xml" == it
            }

            val is_spark=request["is_spark"]?:0

            when{
                request.query.containsKey("groups")->{

                }
                request.query.containsKey("feeds")->{

                }
                request.query.containsKey("favicons")->{

                }
                request.query.containsKey("items")->{
                    val since_id=request["since_id"]?:0
                    val max_id=request["max_id"]?:50
                    var with_ids=emptyList<Long>()
                    request["with_ids"]?.let { ids ->
                        with_ids = ids.split(",").map { it.toLong() }
                    }
                }
                request.query.containsKey("links")->{
                    val offset=request["offset"]?:0
                    val range=request["range"]?:7
                    val page=request["page"]?:1
                }
                request.query.containsKey("unread_item_ids")->{

                }
                request.query.containsKey("saved_item_ids")->{

                }
            }
        })


    }

    private fun responseSearchToRss(request: AsyncHttpServerRequest, response: AsyncHttpServerResponse, isXML:Boolean=true) {
        Timber.d("in Come  responseSearchToRss ${request["text"]} is xml:$isXML")
        val searchString = request["text"]
        val pageNumber = request["page"]?.toIntOrNull()?:0
        val feed= Feed(title = "search:$searchString")
        val feedItems = if(searchString.isNullOrEmpty()) emptyList() else feedItemDao.search(searchString,pageNumber)
        responseFeedToRss(response, feed, feedItems, isXML)
    }

    private fun responseFeedToRss(request: AsyncHttpServerRequest, response: AsyncHttpServerResponse, feedId:Long, isXML:Boolean=true) {
        Timber.d("in Come responseFeedToRss feedId:$feedId is xml:$isXML")
        val feed = feedDao.loadFeed(feedId)
        feed?.let {
            val pageNumber = request.get("page")
            var feedItems: List<FeedItem> = emptyList()
            if (pageNumber.isNullOrEmpty())
                feedItems = feedItemDao.loadFeedItemsInFeedWithPage(feedId)
            else if (pageNumber == "all")
                feedItems = feedItemDao.loadFeedItemsInFeed(feedId)
            else {
                pageNumber.toIntOrNull()?.let { page ->
                    feedItems = feedItemDao.loadFeedItemsInFeedWithPage(feedId, page)
                }
            }
            responseFeedToRss(response, feed, feedItems, isXML)
        }
    }

    private fun responseFeedGroupToRss(response: AsyncHttpServerResponse, feedGroup:List<String>,  isXML:Boolean=false) {
        if (isXML) {
            response.setContentType("application/rss+xml")
            //response.send(SyndFeedOutput().outputString(toSyndFeed(feed, feedItems), true))
        } else {
            //response.send(feedAdapter().toJson(feed.toJsonFeed(feedItems.map { item -> item.convertToJsonFeedItem() })))
        }
    }

    private fun responseFeedToRss(response: AsyncHttpServerResponse, feed:Feed, feedItems: List<FeedItem>, isXML:Boolean=false) {
        if (isXML) {
            response.setContentType("application/rss+xml")
            response.send(SyndFeedOutput().outputString(toSyndFeed(feed, feedItems), true))
        } else {
            response.setContentType("application/json")
            response.send(feedAdapter().toJson(feed.toJsonFeed(feedItems.map { item -> item.convertToJsonFeedItem() })))
        }
    }


    override fun onDestroy() {
        super.onDestroy()
        Timber.d("http server destroy...")
        stopServer()
    }

    /**
     * Start server.
     */
    private fun startServer() {
        if (!running)
            server.listen(PortNumber)
        Timber.d("http server start...")
        running = true
    }

    /**
     * Stop server.
     */
    private fun stopServer() {
        if (running) {
            server.stop()
            notificationServiceCanceled(RSS_ServiceNotification_ID)
        }
        Timber.d("http server stop...")
        running = false
    }
}

fun getRunAddr(appContext: Context):String = "http://${appContext.getIp()}:$PortNumber"

const val  RSS_ServiceNotification_ID=-25412

fun rssServiceNotification(appContext: Context): Notification {
    val notification= appContext.buildNotificationService("start at ${getRunAddr(appContext)}",RSS_ServiceNotification_ID){
        config {
            setContentTitle("Http server($RSS_EXPORT_PATH,$OPML_EXPORT_PATH)")
            //setOnlyAlertOnce(true)
            setOngoing(true)
            addAction(
                android.R.drawable.ic_delete,
                "stop",
                appContext.broadcastPendingIntent<RssNotificationBroadcastReceiver>(ACTION_STOP_HTTP_SERVER)
            )
            setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
        }
    }
    return notification

   /* val webChannel = NotificationChannel(
        "channelIdWeb",
       " getString(R.string.web_service)",
        NotificationManager.IMPORTANCE_DEFAULT
    ).apply {
        enableLights(false)
        enableVibration(false)
        setSound(null, null)
    }

    //向notification manager 提交channel
    notificationManager.createNotificationChannel( webChannel)

    val builder = NotificationCompat.Builder(appContext, "channelIdWeb")
        .setSmallIcon(R.drawable.ic_launcher_background)
        .setOngoing(true)
        .setContentTitle("Http server($RSS_EXPORT_PATH,$OPML_EXPORT_PATH)")
        .setContentText("start at ${getRunAddr(appContext)}")
    builder.addAction(
        android.R.drawable.ic_delete,
        "stop",
        appContext.broadcastPendingIntent<RssNotificationBroadcastReceiver>(ACTION_STOP_HTTP_SERVER)
    )
    builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
    return builder.build()*/
}


/**
 * find the key value in the path and set them in the intent
 *
 * @param routeUrl the matched route path
 * @param givenUrl the given path
 * @param intent   the intent
 * @return the intent
 */
private fun setKeyValueInThePath(routeUrl: String, givenUrl: String, intent: Intent): Intent {
    val routePathSegs: List<String> = Uri.parse(routeUrl).pathSegments
    val givenPathSegs: List<String> = Uri.parse(givenUrl).pathSegments
    for (i in routePathSegs.indices) {
        val seg = routePathSegs[i]
        if (seg.startsWith(":")) {
            val indexOfLeft = seg.indexOf("{")
            val indexOfRight = seg.indexOf("}")
            val key = seg.substring(indexOfLeft + 1, indexOfRight)
            val typeChar = seg[1]
            when (typeChar) {
                'i' -> try {
                    val value = givenPathSegs[i].toInt()
                    intent.putExtra(key, value)
                } catch (e: java.lang.Exception) {
                    Timber.d("解析整形类型失败 " + givenPathSegs[i], e)
                    if (BuildConfig.DEBUG) {
                        throw InvalidValueTypeException(givenUrl, givenPathSegs[i])
                    } else {
                        //如果是在release情况下则给一个默认值
                        intent.putExtra(key, 0)
                    }
                }
                'f' ->                         //float type
                    try {
                        val value = givenPathSegs[i].toFloat()
                        intent.putExtra(key, value)
                    } catch (e: java.lang.Exception) {
                        Timber.d( "解析浮点类型失败 " + givenPathSegs[i], e)
                        if (BuildConfig.DEBUG) {
                            throw InvalidValueTypeException(givenUrl, givenPathSegs[i])
                        } else {
                            intent.putExtra(key, 0f)
                        }
                    }
                'l' ->                         //long type
                    try {
                        val value = givenPathSegs[i].toLong()
                        intent.putExtra(key, value)
                    } catch (e: java.lang.Exception) {
                        Timber.d( "解析长整形失败 " + givenPathSegs[i], e)
                        if (BuildConfig.DEBUG) {
                            throw InvalidValueTypeException(givenUrl, givenPathSegs[i])
                        } else {
                            intent.putExtra(key, 0L)
                        }
                    }
                'd' -> try {
                    val value = givenPathSegs[i].toDouble()
                    intent.putExtra(key, value)
                } catch (e: java.lang.Exception) {
                    Timber.d( "解析double类型失败 " + givenPathSegs[i], e)
                    if (BuildConfig.DEBUG) {
                        throw InvalidValueTypeException(givenUrl, givenPathSegs[i])
                    } else {
                        intent.putExtra(key, 0.0)
                    }
                }
                'c' -> try {
                    val value = givenPathSegs[i][0]
                } catch (e: java.lang.Exception) {
                    Timber.d( "解析Character类型失败" + givenPathSegs[i], e)
                    if (BuildConfig.DEBUG) {
                        throw InvalidValueTypeException(givenUrl, givenPathSegs[i])
                    } else {
                        intent.putExtra(key, ' ')
                    }
                }
                's' -> intent.putExtra(key, givenPathSegs[i])
                else -> intent.putExtra(key, givenPathSegs[i])
            }
        }
    }
    return intent
}

class InvalidValueTypeException(val givenUrl: String, val s: String) : Throwable() {

}

class AsyncHttpSession(val request:AsyncHttpServerRequest, val asyncHttpServerResponse:AsyncHttpServerResponse, val serverContext: Context):
    IHTTPSession {
    override val inputStream: InputStream?
        get()=request.getBody()
/*    override val context: Context
        get() = serverContext*/
    override val cookies: CookieHandler
        get() = CookieHandler.getDefault()
    override val headers: Map<String, String>
        get() = request.headers.multiMap.toSingleMap()
    override val method: Method
        get() = Method.lookup(request.method)!!
    override val parameters: Map<String, List<String>>
        get() = TODO("Not yet implemented")
    override val query: Map<String, String>
        get() = request.query.toSingleMap()
    override val uri: String
        get() = request.url.toString()
    override val remoteIpAddress: String
        get() = ""
    override val requestBody: String
        get() = request.getBody<StringBody>().get()
    override val path: String
        get() = request.path

    override var response: Response?=null
}

fun HttpUriRooterManager.route(session: AsyncHttpSession) {
    Timber.d("come in route:" + session.uri + "method:" + session.method + "\t path:" + session.path)
    process(session,session.uri,session.method)
    val resp = session.response!!
    session.asyncHttpServerResponse.setContentType(resp.mimeType)
    session.asyncHttpServerResponse.code(resp.status.requestStatus)
    session.asyncHttpServerResponse.sendStream(resp.data, resp.contentLength)

}

class Http(val context: Context): AsyncHttpServer() {

    val httpRouter by lazy {
        HttpUriRooterManager()
    }

    fun route(method: Method, path:String, priority:Int, handler: HttpCallHandler){
        httpRouter.addRoute(path,method=method, priority = priority, handler = handler)
        val regexPath = createUriPattern(path)
        addAction(method.name,regexPath) { request, response -> httpRouter.route(AsyncHttpSession(request, response,context)) }
    }

    fun route(method: Method, path:String, priority:Int, handler: HttpCallHandlerFactory){
        httpRouter.addRoute(path,method=method, priority = priority,  handler=handler)
        val regexPath = createUriPattern(path)
        addAction(method.name,regexPath) { request, response -> httpRouter.route(AsyncHttpSession(request, response,context)) }
    }

    fun get( path:String, handler: HttpCallHandler){
        route(Method.GET,path,100, handler)
    }

    fun get( path:String, priority:Int=100, handler: HttpCallHandler){
        route(Method.GET,path,priority, handler)
    }

    fun get( path:String,  handler: HttpCallHandlerFactory){
        route(Method.GET,path,100, handler)
    }
    fun get( path:String, priority:Int=100, handler: HttpCallHandlerFactory){
        route(Method.GET,path,priority, handler)
    }

    fun put( path:String, handler: HttpCallHandler){
        route(Method.PUT,path,100, handler)
    }

    fun put( path:String, handler: HttpCallHandlerFactory){
        route(Method.PUT,path,100, handler)
    }

    fun post( path:String, handler: HttpCallHandler){
        route(Method.POST,path,100, handler)
    }

    fun post( path:String, handler: HttpCallHandlerFactory){
        route(Method.POST,path,100, handler)
    }

    fun delete( path:String, handler: HttpCallHandler){
        route(Method.DELETE,path,100, handler)
    }

    fun delete( path:String, handler: HttpCallHandlerFactory){
        route(Method.DELETE,path,100, handler)
    }

    fun patch( path:String, handler: HttpCallHandler){
        route(Method.PATCH,path,100, handler)
    }

    fun patch( path:String, handler: HttpCallHandlerFactory){
        route(Method.PATCH,path,100, handler)
    }

}
